#region Copyright Statement
//  ------------------------------------------------------------------------------
//  
//    This file is part of The Streambolics Library.
//    Copyright © 2005-2009, Stephan Leclercq & Streambolics
//    All rights reserved.
//  
//    The Streambolics Library is free software; you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation; either version 2 of the License, or
//    (at your option) any later version.
//  
//    The Streambolics Library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//  
//    You should have received a copy of the GNU General Public License
//    along with Streambolics; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//  
//  ------------------------------------------------------------------------------
#endregion

#region Change Log
//  
//  Commit: 11/11/2008 15:57:22 - SN118662190318\Stéphan Leclercq
//    Change: 17B18B1B-07ED-4CCF-B12A-5675042F584C: Miscellaneous Bugs & Enhancements
//    Reviewed: 11/11/2008 15:57:21 - SN118662190318\Stéphan Leclercq
//  
#endregion

using System;
using System.Collections.Generic;
using System.Text;

namespace Streambolics
{
    /// <summary>
    ///     Extensions for the String class.
    /// </summary>
    /// <remarks><para>
    ///     This class contains methods that are missing from the String
    ///     class for it to be useful.
    /// </para><para>
    ///     This is a good example of why extension methods are actually
    ///     a good thing...
    /// </para></remarks>

    public static class StringExtensions
    {
        /// <summary>
        ///     Everyting in <c>s</c> after the first occurrence of aTag
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in <c>s</c> after the last occurrence of aTag.
        ///     </li><li>
        ///         An empty, non null string if the first occurrence of
        ///         aTag is exactly at the end of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in <c>s</c>.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string AfterFirst (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.IndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (p + aTag.Length);
            }
        }

        /// <summary>
        ///     Everyting in <c>s</c> after the last occurrence of aTag
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in <c>s</c> after the last occurrence of aTag.
        ///     </li><li>
        ///         An empty, non null string if aTag is exactly at the end
        ///         of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in <c>s</c>.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string AfterLast (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.LastIndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (p + aTag.Length);
            }
        }

        /// <summary>
        ///     Everyting in <c>s</c> after the last occurrence of aTag, including the
        ///     tag itself.
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in <c>s</c> after the last occurrence of aTag,
        ///         including aTag itself.
        ///     </li><li>
        ///         aTag alone, if aTag is exactly at the end of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in <c>s</c>.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string AfterAndIncludingLast (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.LastIndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (p);
            }
        }

        /// <summary>
        ///     Everyting in <c>s</c> before the first occurrence of aTag
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in <c>s</c> before the first occurrence of aTag.
        ///     </li><li>
        ///         An empty, non null string if aTag is exactly at the end
        ///         of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in s.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string BeforeFirst (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.IndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (0, p);
            }
        }

        /// <summary>
        ///     Everyting in <c>s</c> before the last occurrence of aTag
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in <c>s</c> before the last occurrence of aTag.
        ///     </li><li>
        ///         An empty, non null string if the last occurrence of aTag is 
        ///         exactly at the beginning of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in <c>s</c>.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string BeforeLast (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.LastIndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (0, p);
            }
        }

        /// <summary>
        ///     Everyting in <c>s</c> before the last occurrence of aTag, including
        ///     aTag itself.
        /// </summary>
        /// <param name="s">
        ///     The input string where to search.
        /// </param>
        /// <param name="aTag">
        ///     The tag or delimiter to search.
        /// </param>
        /// <returns>
        ///     Either:
        ///     <ul><li>
        ///         All characters in s before the last occurrence of aTag,
        ///         including aTag itself.
        ///     </li><li>
        ///         aTag alone if the last occurrence of aTag is 
        ///         exactly at the beginning of <c>s</c>.
        ///     </li><li>
        ///         A null string if aTag does not appear in <c>s</c>.
        ///     </li></ul>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if aTag is empty or null, or if <c>s</c> is null.
        /// </exception>

        public static string BeforeAndIncludingLast (this string s, string aTag)
        {
            AssertNotEmpty (aTag, "aTag");
            int p = s.LastIndexOf (aTag);
            if (p < 0)
            {
                return null;
            }
            else
            {
                return s.Substring (0, p + aTag.Length);
            }
        }


        public static string BetweenFirst (this string s, string aStartTag, string aEndTag)
        {
            AssertNotEmpty (aStartTag, "aStartTag");
            AssertNotEmpty (aEndTag, "aEndTag");
            string ss = s.AfterFirst(aStartTag);
            if (ss == null)
            {
                return null;
            }
            else
            {
                return ss.BeforeFirst (aEndTag);
            }
        }

        /// <summary>
        ///     Remove a <b>single</b> occurrence of aTrailer at the
        ///     end of <c>s</c>.
        /// </summary>
        /// <param name="s">
        ///     The string to trim.
        /// </param>
        /// <param name="aTrailer">
        ///     The trailer to remove.
        /// </param>
        /// <returns>
        ///     If <c>s</c> ends with the trailer, the return value is <c>s</c> with the trailer
        ///     removed. Otherwise it is <c>s</c> itself.
        /// </returns>

        public static string RemoveOneTrailing (this string s, string aTrailer)
        {
            return RemoveTrailing (s, aTrailer, 1);
        }

        /// <summary>
        ///     Remove all occurrences of aTrailer at the end of <c>s</c>.
        /// </summary>
        /// <param name="s">
        ///     The string to trim.
        /// </param>
        /// <param name="aTrailer">
        ///     The trailer to remove.
        /// </param>
        /// <returns>
        ///     If <c>s</c> ends with the trailer, the return value is <c>s</c> with 
        ///     all occurrences of the trailer removed. Otherwise it is <c>s</c> itself.
        /// </returns>

        public static string RemoveTrailing (this string s, string aTrailer)
        {
            return RemoveTrailing (s, aTrailer, s.Length);
        }

        /// <summary>
        ///     Remove at most aMax occurrences of aTrailer at the end
        ///     of <c>s</c>.
        /// </summary>
        /// <param name="s">
        ///     The string to modify.
        /// </param>
        /// <param name="aTrailer">
        ///     The trailer to remove.
        /// </param>
        /// <param name="aMax">
        ///     The maximum number of trailers to remove.
        /// </param>
        /// <returns>
        ///     <c>s</c> with at most aMax trailers removed.
        /// </returns>

        public static string RemoveTrailing (this string s, string aTrailer, int aMax)
        {
            int count;
            return RemoveTrailing (s, aTrailer, aMax, out count);
        }

        /// <summary>
        ///     Remove at most aMax occurrences of aTrailer at the end
        ///     of <c>s</c>.
        /// </summary>
        /// <param name="s">
        ///     The string to modify.
        /// </param>
        /// <param name="aTrailer">
        ///     The trailer to remove.
        /// </param>
        /// <param name="aMax">
        ///     The maximum number of trailers to remove.
        /// </param>
        /// <returns>
        ///     <c>s</c> with at most aMax trailers removed.
        ///     aCount contains the number of trailers removed.
        /// </returns>

        public static string RemoveTrailing (this string s, string aTrailer, int aMax, out int aCount)
        {
            AssertNotEmpty (aTrailer, "aTrailer");
            if (aMax <= 0)
            {
                throw new ArgumentException ("Must be a positive integer", "aMax");
            }
            aCount = 0;

            while (aCount < aMax && s.EndsWith (aTrailer))
            {
                aCount++;
                s = s.Substring (0, s.Length - aTrailer.Length);
            }
            return s;
        }

        /// <summary>
        ///     Verifies that an argument is not empty.
        /// </summary>
        /// <param name="s">
        ///     The argument to check.
        /// </param>
        /// <param name="aParamName">
        ///     The name of the argument.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     Thrown if <c>s</c> is empty or null.
        /// </exception>

        public static void AssertNotEmpty (this string s, string aParamName)
        {
            if (String.IsNullOrEmpty (s))
            {
                throw new ArgumentNullException (aParamName);
            }
        }

        /// <summary>
        ///     Prefix all lines with a constant prefix.
        /// </summary>
        /// <param name="s">
        ///     The string to process.
        /// </param>
        /// <param name="aPrefix">
        ///     The prefix to add on each line.
        /// </param>
        /// <returns>
        ///     String <c>s</c> with each line prefixed by aPrefix.
        /// </returns>
        /// <remarks><para>
        ///     The output string always contains a trailing end of line.
        /// </para></remarks>

        public static string LinesPrefixedWith (this string s, string aPrefix)
        {
            StringList sl = new StringList (s);
            sl.PrefixWith (aPrefix);
            return sl.Text;
        }

        /// <summary>
        ///     The string <c>s</c> as a collection of lines
        /// </summary>
        /// <param name="s">
        ///     The string.
        /// </param>
        /// <returns>
        ///     A StringList composed of the lines of <c>s</c>.
        /// </returns>

        public static StringList Lines (this string s)
        {
            return new StringList (s);
        }

        /// <summary>
        ///     A string with fixed length.
        /// </summary>
        /// <param name="s">
        ///     The string to resize
        /// </param>
        /// <param name="l">
        ///     The exact length needed.
        /// </param>
        /// <returns>
        ///     A string of exactly <c>l</c> characters, beginning with <c>s</c>.
        ///     If <c>s</c> is too long, it is truncated.
        ///     If <c>s</c> is too short, it is padded with trailing spaces.
        /// </returns>

        public static string Fix (this string s, int l)
        {
            if (s.Length == l)
            {
                return s;
            }
            else if (s.Length > l)
            {
                return s.Substring (0, l);
            }
            else
            {
                StringBuilder sb = new StringBuilder (s);
                sb.Append (' ', l - s.Length);
                return sb.ToString ();
            }
        }

        /// <summary>
        ///     Pad the string <c>s</c> so it has at least <c>l</c> characters.
        /// </summary>
        /// <param name="s">
        ///     The string to pad.
        /// </param>
        /// <param name="l">
        ///     The total length.
        /// </param>
        /// <returns>
        ///     A string of length <c>l</c> at least, beginning with <c>s</c>.
        ///     If <c>s</c> is too short, trailing spaces are added.
        /// </returns>

        public static string Padded (this string s, int l)
        {
            if (s.Length >= l)
            {
                return s;
            }
            else
            {
                StringBuilder sb = new StringBuilder (s);
                sb.Append (' ', l - s.Length);
                return sb.ToString ();
            }
        }

        /// <summary>
        ///     Inserts spaces between words
        /// </summary>
        /// <param name="s">
        ///     A string using camel capitalization (Example: AStringUsingCamelCapitalization)
        /// </param>
        /// <returns>
        ///     The sting where each group of letters beginning with a capital is
        ///     a word and separated by a space from the previous one.
        /// </returns>

        public static string SeparateCamelWords (this string s)
        {
            StringBuilder sb = new StringBuilder ();
            bool Previous = true;

            foreach (char c in s)
            {
                bool IsUpper = c >= 'A' && c <= 'Z';
                if (IsUpper && !Previous)
                {
                    sb.Append (' ');
                }
                Previous = IsUpper;
                sb.Append (c);
            }
            return sb.ToString ();
        }

        public static IEnumerable<string> SubStrings (this string s)
        {
            StringBuilder sb = new StringBuilder ();
            foreach (char c in s)
            {
                if (c == '\0')
                {
                    if (sb.Length == 0)
                    {
                        yield break;
                    }
                    else
                    {
                        yield return sb.ToString ();
                        sb = new StringBuilder ();
                    }
                }
                else
                {
                    sb.Append (c);
                }
            }

            if (sb.Length > 0)
            {
                yield return sb.ToString ();
            }
        }

        public static int ToInteger (this string s)
        {
            return Int32.Parse (s);
        }

        public static byte[] ToAsciiBytes (this string s)
        {
            return new System.Text.ASCIIEncoding ().GetBytes (s);
        }

        /// <summary>
        ///     The last <c>l</c> characters of <c>s</c>
        /// </summary>
        /// <param name="s">
        ///     The string to cut
        /// </param>
        /// <param name="l">
        ///     The maximum number of characters to extract
        /// </param>
        /// <returns>
        ///     The last <c>l</c> characters of <c>s</c>, or the whole of <c>s</c> if it
        ///     is shorter than <c>l</c>.
        /// </returns>

        public static string Last (this string s, int l)
        {
            if (s.Length <= l)
            {
                return s;
            }
            else
            {
                return s.Substring (s.Length - l, l);
            }
        }

        /// <summary>
        ///     The first <c>l</c> characters of <c>s</c>
        /// </summary>
        /// <param name="l">
        ///     The string to cut
        /// </param>
        /// <param name="n">
        ///     The maximum number of characters to extract
        /// </param>
        /// <returns>
        ///     The first <c>l</c> characters of <c>s</c>, or the whole of <c>s</c> if it
        ///     is shorter than <c>l</c>.
        /// </returns>

        public static string First (this string s, int l)
        {
            if (s.Length <= l)
            {
                return s;
            }
            else
            {
                return s.Substring (0, l);
            }
        }

        public static string Format (this string s, params object[] args)
        {
            return String.Format (s, args);
        }

        public static string ToQuotedString (this object o)
        {
            return "«" + o.ToString () + "»";
        }
    }
}
